<script setup lang="ts">
// 导入Vue相关功能
import { ref, onBeforeUnmount, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
// 导入状态管理仓库
import { useUserStore, useSettingStore, useMettingStore } from '@/stores/index'
// 导入socket.io客户端
import { io } from 'socket.io-client'
import { Socket } from 'socket.io-client'

// 导入视图组件
import MeetingStatus from '../components/MeetingStatus/index.vue'
import VideoArea from '../components/VideoArea/index.vue'
import VideoSetting from '../components/VideoSetting/index.vue'
import CommentTab from '../components/CommentTab/index.vue'
import CommentContent from '../components/CommentContent/index.vue'
import CommentSend from '../components/CommentSend/index.vue'

// 导入API服务
import {
  deleteParticipantsAPI,
  getChatLastAPI,
  getParticipantsAPI
} from '@/services/room/room'
import { getRoomInfoAPI } from '@/services/room/room'
import { postHistoryAddAPI } from '@/services/history/history'
import { HistoryAddParamsType } from '@/services/history/type'
import { onMounted } from 'vue'

import {
  GetChatLastResponseData,
  Chats,
  GetRoomsResponseData,
  RoomInfo,
  ParticipantsResponseData,
  ParticipantsItem
} from '@/services/room/type'

// 初始化状态管理仓库
const userStore = useUserStore()
const settingStore = useSettingStore()
const mettingStore = useMettingStore()
// 获取当前路由参数
const $route = useRoute()
const $router = useRouter()
// 将房间ID设置为响应式变量
const roomId = $route.path.split('/')[2]
// 初始化WebSocket连接
const sock: Socket = io('http://localhost:3001')

// 聊天界面宽度的响应式变量
const sendMsgWidth = ref('25%')

// 监听设置中聊天界面开关的变化，动态调整聊天界面宽度
watch(
  () => settingStore.settingMeeting.sendMsgStatus,
  newStatus => {
    sendMsgWidth.value = newStatus ? '25%' : '0'
  }
)

type roomListItem = {
  id: string
  account: string
  avatar: string
}
interface PeerList {
  [key: string]: RTCPeerConnection
}

interface dataChannelList {
  [key: string]: RTCDataChannel
}
interface StreamItem {
  account: string
  avatar: string
  stream: MediaStream | null
}

/**
 * 获取 flag 来判断用户是否是房主
 * 0 是房主
 * 1 不是房主
 */
const { flag } = $route.query

// 本机用户名
const account = ref(userStore.userInfo.userInfo.username)
// 本机头像
const avatar = ref(userStore.userInfo.userInfo.avatar)
// 存储 Peer 实例
let peerList: PeerList = {}
// 存储 dataChannel 通道
const dataChannelList: dataChannelList = {}
// 存储远端视频流
const remoteStream = ref<StreamItem[]>([])
// 存储本地视频流
const localStream = ref<MediaStream | null>(null)
// 音频和视频的状态
const streamStatus = ref()

const handleBeforeUnload = async (event: BeforeUnloadEvent) => {
  const endTime = getTimeNow()
  historyAddParams.value = {
    MID: roomInfo.value.MID,
    mRoomId: userStore.userInfo.userInfo.roomId,
    roomId: roomId,
    title: roomInfo.value.title,
    type: flag as string,
    startTime: startTime.value,
    endTime,
    nearTime: startTime.value,
    host: roomInfo.value.account
  }
  await Promise.all([
    // 删除参与者信息
    deleteParticipantsAPI(roomId, account.value),
    // 添加历史记录
    postHistoryAddAPI(historyAddParams.value)
  ])
  console.log('刷新导致离开房间')

  // 发送 leave 离开房间消息
  sock.emit('leaveRoom', roomId, account.value)
  event.preventDefault()
  event.returnValue
}

onMounted(async () => {
  // 绑定刷新事件
  window.addEventListener('beforeunload', handleBeforeUnload)
  // 初始化获取信息
  await initializeMeeting()
})

// 在组件卸载前调用，删除参与者信息
onBeforeUnmount(async () => {
  const endTime = getTimeNow()
  historyAddParams.value = {
    MID: roomInfo.value.MID,
    mRoomId: userStore.userInfo.userInfo.roomId,
    roomId: roomId,
    title: roomInfo.value.title,
    type: flag as string,
    startTime: startTime.value,
    endTime,
    nearTime: startTime.value,
    host: roomInfo.value.account
  }
  await Promise.all([
    // 删除参与者信息
    deleteParticipantsAPI(roomId, account.value),
    // 添加历史记录
    postHistoryAddAPI(historyAddParams.value)
  ])
  console.log('离开页面导致离开房间')

  // 发送 leave 离开房间消息
  sock.emit('leaveRoom', roomId, account.value)
  // 移除监听事件
  window.removeEventListener('beforeunload', handleBeforeUnload)
})

const initializeMeeting = async () => {
  // 重置视频和语音的状态
  settingStore.resetSettingMeeting()
  // 连接房间
  connectRoom()
  await Promise.all([
    // 获取房间详情信息
    getRoomInfoData(),
    // 获取房间内人数信息
    getParticipantsData()
  ])
}

const roomInfo = ref<RoomInfo>({} as RoomInfo)

const getRoomInfoData = async () => {
  const res: GetRoomsResponseData = await getRoomInfoAPI(roomId)
  roomInfo.value = res.data.roomInfo[0]
}

const getParticipantsData = async () => {
  const res: ParticipantsResponseData = await getParticipantsAPI(roomId)
  participants.value = res.data.participants
}
const participants = ref<ParticipantsItem[]>([])

// 存储开始的时间
const startTime = ref<string>('')

const getTimeNow = () => {
  const currentDate = new Date()
  const timezoneOffset = currentDate.getTimezoneOffset() * 60000 // 获取时区偏移值

  const adjustedTime = currentDate.getTime() - timezoneOffset // 调整时间
  const adjustedDate = new Date(adjustedTime) // 创建新的调整后的 Date 对象
  const formattedDate = adjustedDate
    .toISOString()
    .slice(0, 19)
    .replace('T', ' ')
  return formattedDate
}
const historyAddParams = ref({} as HistoryAddParamsType)

// 连接房间
const connectRoom = () => {
  // 监听与信令服务器的连接成功事件
  sock.on('connect', async () => {
    console.log('前端连接成功')
    // 获取当前时间
    const formattedDate = getTimeNow()
    // 将当前时间保存起来
    startTime.value = formattedDate
    // 发送加入房间事件
    if (flag === '1') {
      sock.emit('joinRoom', roomId, account.value, avatar.value)
    } else if (flag === '0') {
      sock.emit('createRoom', roomId, account.value, avatar.value)
    }
  })
}

/**
 * roomList 当前房间的信息
 * recAccount 接收信息的用户名
 * id 接收信息的用户的id
 */
sock.on('joined', (roomList, recAccount, id) => {
  // 根据收到信息方是谁去做判断
  if (recAccount === account.value) {
    // 如果是本机端收到本机端发来的joined
    // 需要分别创建对应到客户端的peer实例
    // eg: C-A C-B
    initPeer(roomList, recAccount, id, true)
  } else {
    // 其他客户端收到本机端发来的joined
    // 只需要创建一个对应到本机端的peer实例
    // eg: A-C
    initPeer(roomList, recAccount, id, false)
  }
})

// 初始化Peer连接的函数
const initPeer = (
  roomList: any, // 房间列表，包含所有参与者的信息
  recAccount: string, // 接收方的账户名
  _id: string, // 发送方的唯一标识符
  isLocalFlag: boolean // 标志位，用于判断是本地端还是远端
) => {
  // 设置WebRTC的配置对象，包括ICE服务器等信息
  const configuration = {
    iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
  }

  // 判断是本地端还是远端
  if (isLocalFlag) {
    // 如果是本地端
    roomList
      // 过滤出除了本地端之外的所有其他客户端
      .filter((item: roomListItem) => item.account !== account.value)
      // 对每一个远端客户端创建一个Peer连接
      .forEach((item: roomListItem) => {
        // 生成唯一标识符
        const uniqueId = `${recAccount}-${item.account}`
        // 调用创建Peer连接的函数
        createPeerConnection(uniqueId, item.account, item.avatar, configuration)
      })
  } else {
    // 如果是远端
    // 生成唯一标识符
    const uniqueId = `${account.value}-${recAccount}`
    // 获取接收方的媒体流
    const recAvatar = roomList.find(
      (item: roomListItem) => item.account === recAccount
    ).avatar
    // 调用创建Peer连接的函数
    createPeerConnection(uniqueId, recAccount, recAvatar, configuration)
  }
}

// 创建一个 RTCPeerConnection 实例，并设置其 ontrack 事件处理函数
const createPeerConnection = (
  uniqueId: string, // 唯一标识符，用于区分不同的peer连接
  account: string, // 对方账户名
  avatar: string, // 对方的媒体流（可能是视频/音频流）
  configuration: RTCConfiguration // WebRTC配置对象，包括ICE服务器等信息
) => {
  // 创建一个新的 RTCPeerConnection 实例
  const peer = new RTCPeerConnection(configuration)
  // 将这个新创建的peer实例存储到一个全局的peerList对象中，键是唯一标识符，值是peer实例
  peerList[uniqueId] = peer
  console.log(peerList)

  // 创建一个新的 dataChannel 实例，并设置其 onmessage 事件处理函数
  const dataChannel = peer.createDataChannel('textChannel')
  dataChannel.onopen = () => {
    console.log('Data channel opened')
  }
  dataChannelList[uniqueId] = dataChannel

  // 先判断在存储之前是否存在相同的视频流
  const isHas = remoteStream.value.findIndex(item => item.account === account)
  // 将对方的账户名和媒体流信息存储到一个全局的remoteStream数组中
  isHas === -1 ? remoteStream.value.push({ account, avatar, stream: null }) : ''
  // 设置dataChannel的onmessage事件处理函数，这个函数会在接收到对方发送的数据时触发
  dataChannel.onmessage = event => {
    // 获取对方发送的数据
    let data = event.data
    console.log(event)
    console.log(dataChannel)

    // 判断对方发送的数据是否是字符串
    if (typeof data === 'string') {
      // 如果是字符串，则将其转换为对象
      data = JSON.parse(data)
    }
  }
  dataChannel.onerror = error => {
    console.error('Data Channel Error:', error)
  }

  // 设置peer实例的ontrack事件处理函数，这个函数会在接收到对方媒体流的轨道时触发
  peer.ontrack = async (event: RTCTrackEvent) => {
    if (event.track) {
      // 从事件对象中获取媒体轨道，并用它创建一个新的媒体流
      const stream = new MediaStream([event.track])
      // 在remoteStream数组中找到对应账户名的流信息对象
      const newStream = remoteStream.value.find(v => v.account === account)
      // 如果找到了，就用新的媒体流替换掉旧的媒体流
      if (newStream) {
        newStream.stream = stream
      }
    }
  }
  // 返回创建的peer实例
  return peer
}

// 当用户点击开启视频或者开启语音，重新生成SDP信息
const exchangeSDP = async () => {
  // 获取本地视频流
  const stream = await getLocalStream(streamStatus.value)
  localStream.value = stream
  const track = localStream.value?.getTracks()[0]
  // 给关于本机的每一个peer实例添加视频流
  for (const peer in peerList) {
    peerList[peer].addTrack(track)
  }
  createOffer(peerList, roomId)
}

// offer SDP 处理
const createOffer = async (peerList: PeerList, roomId: string) => {
  try {
    Object.keys(peerList).forEach(async _unique => {
      // 创建 offer
      const offer = await peerList[_unique].createOffer()
      // 设置本地描述
      await peerList[_unique].setLocalDescription(offer)
      // 分别给房间内其他客户端发送 offer
      sock.emit('message', _unique, roomId, {
        type: 'offer',
        sdp: offer
      })
    })
  } catch (error) {
    console.log('offer', error)
  }
}

// answer SDP 处理
const createAnswer = async (
  peer: RTCPeerConnection,
  _unique: string,
  roomId: string
) => {
  try {
    // 创建 answer
    const answer = await peer.createAnswer()
    // 设置本端描述
    await peer.setLocalDescription(answer)
    // 给发送方返回answer
    sock.emit('message', _unique, roomId, {
      type: 'answer',
      sdp: answer
    })
  } catch (error) {
    console.log('answer', error)
  }
}

// candidate ICE 处理
const createCandidate = async (
  peer: RTCPeerConnection,
  _unique: string,
  roomId: string,
  flag: boolean
) => {
  try {
    // 生成 candidate 信息
    peer.onicecandidate = (event: any) => {
      if (event.candidate) {
        // 发送 candidate 信息
        sock.emit('message', _unique, roomId, {
          type: 'candidate',
          candidate: event.candidate,
          flag
        })
      }
    }
  } catch (error) {
    console.log('candidate', error)
  }
}

// 处理 message 事件
/**
 * _unique 唯一标识符
 * roomId 房间id
 * msg 信息
 */
sock.on('message', async (_unique: string, roomId, msg) => {
  const message = msg
  // 处理 offer answer candidate 事件
  if (_unique.split('-')[1] === account.value) {
    // 因为发送过来的是对面的_unique 所以在本地找的时候得反着来
    _unique = _unique.split('-').reverse().join('-')
    const peer = peerList[_unique]
    if (message.type === 'offer') {
      const offer = msg.sdp
      console.log(`${_unique}收到offer`, offer)
      // 设置远端描述
      await peer.setRemoteDescription(offer)
      // 创建和发送answer
      createAnswer(peer, _unique, roomId)
    } else if (message.type === 'answer') {
      const answer = msg.sdp
      console.log(`${_unique}收到answer`, answer)
      // 设置远端描述
      await peer.setRemoteDescription(answer)
      // 创建和发送 candidate 信息
      createCandidate(peer, _unique, roomId, true)
    } else if (message.type === 'candidate') {
      const candidate = msg.candidate
      console.log(`${_unique}收到candidate`, candidate)
      // 添加 candidate 信息
      await peer.addIceCandidate(candidate)
      // 创建和发送 candidate 信息
      flag && createCandidate(peer, _unique, roomId, false)
    }
  }
})

sock.on(
  'leaved',
  async (_roomId: string, _userId: string, recAccount: string) => {
    console.log(recAccount, '离开了房间')
    // 判断是不是本机端
    if (account.value !== recAccount) {
      // 不是本机端
      // 删除对应的视频流
      remoteStream.value = remoteStream.value.filter(
        stream => stream.account !== recAccount
      )

      // 删除对应的peer实例
      for (const key in peerList) {
        if (
          peerList.hasOwnProperty(key) &&
          typeof peerList[key] === 'object' &&
          peerList[key] !== null
        ) {
          // 根据key去判断
          if (key.split('-')[1] === recAccount) {
            peerList[key].close()
            delete peerList[key]
          }
        }
      }
      console.log(peerList)
    } else {
      // 是本机端
      // 关闭peer实例连接
      for (const key in peerList) {
        peerList[key].close()
      }
      setTimeout(() => {
        $router.push({
          path: `/home/joinMeeting`
        })
      }, 700)
    }
  }
)

const roomIsClose = ref(false)

sock.on('endRoom', async () => {
  // 恢复初始化
  peerList = {}
  for (const key in peerList) {
    peerList[key].close()
  }
  remoteStream.value = []
  roomIsClose.value = true
  ElMessage.warning('房间已经结束')
  setTimeout(() => {
    $router.push({
      path: `/home/joinMeeting`
    })
  }, 700)
})

interface IUser {
  userId: string
  account: string
}

sock.on('commentMsg', (recipientType: IUser | 'all', senderAccount: string) => {
  console.log(recipientType)

  // 判断是否需要获取最新聊天数据
  const shouldGetChatData =
    senderAccount === account.value || // 发送者是自己
    recipientType === 'all' ||
    recipientType.account === account.value // recipientType 包含 'all' 或 当前用户

  if (shouldGetChatData) {
    getChatLastData()
  }
})

const chatList = ref<Chats>([])

// 获取聊天记录最后一条信息的函数
const getChatLastData = async () => {
  const res: GetChatLastResponseData = await getChatLastAPI(roomId)
  chatList.value?.push(res.data.chats[0])
}

// 获取本地音视频流
const getLocalStream = async (status: any) => {
  const stream = await navigator.mediaDevices.getUserMedia(status)
  localStream.value = stream
  return stream
}

// 关闭本地音视频流
const closeLocalStream = () => {
  if (localStream.value) {
    localStream.value.getTracks().forEach((track: MediaStreamTrack) => {
      track.stop()
    })
    localStream.value = null
  }
}

// 语音或者视频状态改变
watch(
  () => mettingStore.meetingStatus,
  newVal => {
    if (newVal.video || newVal.audio) {
      // 获取本地视频或者音频流
      streamStatus.value = newVal
      exchangeSDP()
    } else {
      closeLocalStream()
    }
  },
  {
    deep: true
  }
)
</script>
<template>
  <div class="meeting_container">
    <div class="video_box">
      <MeetingStatus :roomInfo="roomInfo"></MeetingStatus>
      <VideoArea
        :remoteStream="remoteStream"
        :localStream="localStream"
        :account="account"
        :avatar="avatar"
      ></VideoArea>
      <VideoSetting :sock="sock" :roomId="roomId"></VideoSetting>
    </div>
    <div class="comment_box" :style="{ width: sendMsgWidth }">
      <CommentTab></CommentTab>
      <CommentContent :chatList="chatList" :account="account"></CommentContent>
      <CommentSend :sock="sock" :roomId="roomId"></CommentSend>
    </div>
  </div>
</template>

<style scoped lang="less">
@import '../../../assets/less/index.less';
.meeting_container {
  display: flex;
  height: 100vh;
  .video_box {
    flex: 1;
    height: 100%;
  }
  .comment_box {
    transition: all 0.5s;
    border-left: @border-grey;
    width: 25%;
    height: 100%;
    display: flex;
    flex-direction: column;
  }
}
</style>
